package test;

import static org.junit.Assert.*;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import dsa.LinkedList;
import dsa.ListIterator;

public class WinstonCheongTest{
	LinkedList list;
	ListIterator li;
	private static final int INITIALIZATION_SIZE = 10;

	private static final boolean TEST_EXCEPTIONS = false;

	@Before
	public void setUp() throws Exception{
		list = new LinkedList();
		for(int i = 0; i < INITIALIZATION_SIZE; i++)
			list.addLast(i);
		li = list.listIterator(0);
	}

	// sets up the linked list using a second configuration.
	private void setUp2(){
		list = new LinkedList();
		list.addLast("This");
		list.addLast("is");
		list.addLast("a");
		list.addLast("test");
	}

	@After
	public void tearDown() throws Exception{}

	@Test
	public void addtest(){
		// check addfirst initialization.
		assertEquals(list.get(0), 0);
		assertEquals(list.get(9), 9);
		if(TEST_EXCEPTIONS)
			try{
				assertNotNull(list.get(10));
				fail("Handled out of bounds index without throwing exception");
			}catch(IndexOutOfBoundsException e){
				assertTrue(true);
			}

		for(int i = 10; i < 20; i++)
			list.addLast(i);
		assertEquals(list.get(0), 0);
		assertEquals(list.get(19), 19);

	}

	@Test
	public void removeTest(){
		assertEquals(list.remove(4), 4);
		assertEquals(list.get(4), 5);
		if(TEST_EXCEPTIONS)
			try{
				assertNotNull(list.remove(list.size()));
				fail("Handled out of bounds index without throwing exception");
			}catch(IndexOutOfBoundsException e){
				assertTrue(true);
			}
		assertEquals(list.removeFirst(), 0);
		assertEquals(list.get(0), 1);
		assertEquals(list.removeLast(), 9);
		assertEquals(list.get(list.size() - 1), 8);
	}

	@Test
	public void sizeTest(){
		assertEquals(list.size(), 10);
		list.removeFirst();
		assertEquals(list.size(), 9);
		list.remove(7);
		assertEquals(list.size(), 8);
		list.addLast(20);
		assertEquals(list.size(), 9);
	}

	@Test
	public void indexOfTest(){
		// basics
		assertTrue(list.indexOf(20) == -1);
		assertTrue(list.indexOf(5) == 5);

		// test that removal absolutely gets rid of data
		Object i = list.removeFirst();
		assertTrue(list.indexOf(i) == -1);
		i = list.removeLast();
		assertTrue(list.indexOf(i) == -1);

		// System.out.println(list.indexOf(null));
	}

	@Test
	public void containsTest(){
		assertTrue(list.contains(5));
		assertFalse(list.contains(309));
		// remove and test again
		list.remove(list.indexOf(5));
		assertFalse(list.contains(5));
	}

	@Test
	public void getsTest(){
		assertEquals(list.getFirst(), 0);
		assertEquals(list.getLast(), 9);
		list.removeFirst();
		list.removeLast();
		assertEquals(list.getFirst(), 1);
		assertEquals(list.getLast(), 8);
	}

	@Test
	public void cloneTest(){
		LinkedList clone = (LinkedList) list.clone();
		assertTrue(clone.size() == list.size());

		// test for separation of list from clone.
		list.remove(3);
		assertFalse(clone.size() == list.size());

		// test that modification of elements aren't entangled.
		list.set(0, 10);
		// System.out.println(list);
		// System.out.println(clone);
		assertFalse(clone.contains(10));

		setUp2();

		clone = (LinkedList) list.clone();
		list.set(0, "that");
		// System.out.println(list);
		// System.out.println(clone);
		assertFalse(clone.contains("that"));

		// test that modification of modifiable element is entangled...
		list = new LinkedList();
		list.addFirst(new int[]{1, 2, 3, 4, 5});
		clone = (LinkedList) list.clone();
		int[] ints = (int[]) list.get(0);
		ints[1] = 1;
		assertTrue(clone.contains(ints));
		assertTrue(((int[]) clone.getFirst())[1] == 1);

	}

	@Test
	public void setTest(){
		assertEquals(list.set(4, 5), 4);
		assertFalse(list.contains(4));

		list.set(1, 5);
		assertEquals(list.indexOf(5), 1);
	}

	@Test
	public void toArrayTest(){
		Object[] arr = list.toArray();

		// ensure data correct
		for(int i = 0; i < arr.length; i++){
			// assertEquals((int) arr[i], 9 - i); //doesn't work for non Java7
			assertEquals(((Integer) arr[i]).intValue(), i);
		}

		// ensure data aren't entangled
		list.set(0, 5);
		assertEquals(arr[0], 0);

		setUp2();
		arr = list.toArray();
		list.set(0, "that");
		assertEquals(arr[0], "This");
	}

	// -------- List Iterator Tests ---------- //
	@Test
	public void hasNextTest(){
		int count = 0;
		while(li.hasNext()){
			assertNotNull(li.next());
			count++;
		}
		assertEquals(count, list.size());

		li = list.listIterator(list.size()); // this should work..

		assertEquals(li.hasNext(), false);
	}

	@Test
	public void hasPreviousTest(){
		assertEquals(li.hasPrevious(), false);
		int count = 0;
		li = list.listIterator(list.size()); // set listIterator to point to last position

		while(li.hasPrevious()){
			assertNotNull(li.previous());
			count++;
		}
		assertEquals(count, list.size());

	}

	@Test
	public void nextTest(){
		for(int i = 0; i < list.size(); i++)
			assertEquals(li.next(), i);
	}

	@Test
	public void previousTest(){
		li = list.listIterator(list.size());
		for(int i = list.size() - 1; i >= 0; i--){
			assertEquals(li.previous(), i);
		}
	}

	@Test
	public void mixedMovementTest(){
		Object o1;
		Object o2;
		while(li.hasNext()){
			o1 = li.next();
			o2 = li.previous();
			assertEquals(o1, o2);
			li.next();
		}
		// now pointing to end

		while(li.hasPrevious()){
			o1 = li.previous();
			o2 = li.next();
			assertEquals(o1, o2);
			li.previous();
		}
	}

	@Test
	public void addTest() throws Exception{
		li.add(10);
		assertEquals(list.size(), 11); // size increases
		assertEquals(li.previous(), 10); // number was added to previous
		li.add(11);
		assertNotSame(li.next(), 11);

		assertEquals(list.size(), 12); // ensure size of list has changed

		// attempt adding from middle
		setUp(); // reset
		li = list.listIterator(5);
		li.add(12);
		assertEquals(12, li.previous());
		assertEquals(4, li.previous());
		li.next();
		li.next();
		assertEquals(5, li.next());

		// attempt adding from end
		li = list.listIterator(list.size());
		li.add(10);
		assertEquals(false, li.hasNext());
		assertEquals(10, li.previous());
		assertEquals(9, li.previous());

		// ensure index values increase
		int next = li.nextIndex();
		int prev = li.previousIndex();
		li.add(12);
		assertEquals(next + 1, li.nextIndex());
		assertEquals(prev + 1, li.previousIndex());

		list = new LinkedList();
		li = list.listIterator(0);
		li.add(0);
		assertEquals(list.size(), 1);

	}

	@Test
	public void listIteratorConstructorTest(){
		assertEquals(0, li.next());

		for(int i = 0; i < list.size(); i++){
			li = list.listIterator(i);
			assertEquals(i, li.next());
		}

		for(int i = 1; i <= list.size(); i++){
			li = list.listIterator(i);
			assertEquals(i - 1, li.previous());
		}

		li = list.listIterator(0);
		assertEquals(false, li.hasPrevious());

		li = list.listIterator(list.size());
		assertEquals(false, li.hasNext());
	}

	@Test
	public void setTest1(){
		// ensure doesn't do anything to list when called improperly
		li.set(10);
		assertFalse(list.contains(10));

		li = list.listIterator(5);
		li.set(10);
		assertFalse(list.contains(10));

		li.previous();
		li.remove();
		li.set(10);
		assertFalse(list.contains(10));

		li.previous();
		li.previous();
		li.add(-1);
		li.set(10);
		assertFalse(list.contains(10));
	}

	@Test
	public void setTest2(){
		// check setting from next direction
		li = list.listIterator(5);
		li.next();
		li.set(-5);

		assertFalse(list.contains(5));
		assertEquals(-5, li.previous());

		// check setting from previous direction
		li = list.listIterator(3);
		li.previous();
		li.set(-2);
		assertFalse(list.contains(2));
		assertEquals(-2, li.next());
	}

	@Test
	public void removeTest1(){
		// ensure doesn't do anything to list when called improperly
		li.remove();
		assertEquals(INITIALIZATION_SIZE, list.size());

		li = list.listIterator(5);
		li.remove();
		assertEquals(INITIALIZATION_SIZE, list.size());

		li = list.listIterator(list.size());
		li.remove();
		assertEquals(INITIALIZATION_SIZE, list.size());

		li.add(10);
		li.remove();
		assertEquals(INITIALIZATION_SIZE + 1, list.size());
	}

	@Test
	public void removeTest2(){
		// ensure removes correctly when called correctly
		li.next();
		li.remove();
		assertEquals(INITIALIZATION_SIZE - 1, list.size()); // size was diminished

		// check indexes correct
		assertEquals(-1, li.previousIndex());
		assertEquals(0, li.nextIndex());

		// check that entry no longer exists
		assertEquals(1, li.next()); // next value is correct
		li.previous();// move back
		assertFalse(li.hasPrevious());
		assertEquals(0, li.nextIndex());

		// ensure changes occurred to LinkedList
		assertEquals(INITIALIZATION_SIZE - 1, list.size());
	}

	public void removeTest3() throws Exception{
		// test removal from center in multiple directions

		// removal from next direction
		li = list.listIterator(5);
		li.next();
		li.remove();

		// check indexes correct
		assertEquals(li.nextIndex(), 5);
		assertEquals(li.previousIndex(), 4);

		// check surroundings
		assertEquals(li.previous(), 4);
		assertEquals(li.next(), 6);

		// check entry no longer exists
		assertFalse(list.contains(5));

		setUp(); // reset

		li = list.listIterator(5);
		li.next();
		li.remove();

		// check indexes correct
		assertEquals(li.nextIndex(), 5);
		assertEquals(li.previousIndex(), 4);

		// check surroundings
		assertEquals(li.next(), 6);
		assertEquals(li.previous(), 4);

		// check entry no longer exists
		assertFalse(list.contains(5));

		setUp(); // reset

		// test removal from previous direction
		li = list.listIterator(5);
		li.previous();
		li.remove();

		// check indexes correct
		assertEquals(li.nextIndex(), 4);
		assertEquals(li.previousIndex(), 3);

		// check surroundings
		assertEquals(li.previous(), 3);
		assertEquals(li.next(), 5);

		setUp(); // reset

		// test removal from previous direction
		li = list.listIterator(5);
		li.previous();
		li.remove();

		// check indexes correct
		assertEquals(li.nextIndex(), 4);
		assertEquals(li.previousIndex(), 3);

		// check surroundings
		assertEquals(li.next(), 5);
		assertEquals(li.previous(), 3);
	}

	@Test
	public void nextIndexTest(){
		assertEquals(li.nextIndex(), 0);

		while(li.hasNext()){
			Object o = li.next();
			assertEquals(li.nextIndex(), list.indexOf(o) + 1);
		}
		assertEquals(li.nextIndex(), list.size()); // at last position

		li = list.listIterator(list.size());
		assertEquals(li.nextIndex(), list.size());
	}

	@Test
	public void previousIndexTest(){
		assertEquals(li.previousIndex(), -1);

		while(li.hasNext()){
			Object o = li.next();
			assertEquals(li.previousIndex(), list.indexOf(o));
		}

		// at last position
		assertEquals(li.previousIndex(), list.size() - 1);
	}

	@Test
	public void combinedIndexTest(){
		while(li.hasNext()){
			assertEquals(li.nextIndex(), li.previousIndex() + 1);
			li.next();
		}
		assertEquals(li.nextIndex(), li.previousIndex() + 1);
	}

}
